package com.hqd.jjvm.jnative.misc;

import com.hqd.jjvm.jnative.JNativeRegistry;
import com.hqd.jjvm.rtda.JThread;
import com.hqd.jjvm.rtda.LocalVars;
import com.hqd.jjvm.rtda.StackFrame;
import com.hqd.jjvm.rtda.heap.JField;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.slot.RefSlot;
import com.hqd.jjvm.rtda.slot.Slot;

public class JUnsafeNativeRegistry extends JNativeRegistry {
    private static final JUnsafeNativeRegistry instance = new JUnsafeNativeRegistry();
    protected static final String CLASS_PATH = "sun/misc/Unsafe";

    static {
        registry(CLASS_PATH, "registerNatives", "()V", JNativeRegistry::registerNatives);
        registry(CLASS_PATH, "arrayBaseOffset", "(Ljava/lang/Class;)I", JUnsafeNativeRegistry::arrayBaseOffset);
        registry(CLASS_PATH, "arrayIndexScale", "(Ljava/lang/Class;)I", JUnsafeNativeRegistry::arrayIndexScale);
        registry(CLASS_PATH, "addressSize", "()I", JUnsafeNativeRegistry::addressSize);
        registry(CLASS_PATH, "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", JUnsafeNativeRegistry::objectFieldOffset);
        registry(CLASS_PATH, "compareAndSwapObject", "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z", JUnsafeNativeRegistry::compareAndSwapObject);
        registry(CLASS_PATH, "getIntVolatile", "(Ljava/lang/Object;J)I", JUnsafeNativeRegistry::getIntVolatile);
        registry(CLASS_PATH, "compareAndSwapInt", "(Ljava/lang/Object;JII)Z", JUnsafeNativeRegistry::compareAndSwapInt);
        registry(CLASS_PATH, "getObject", "(Ljava/lang/Object;J)Ljava/lang/Object;", JUnsafeNativeRegistry::getObject);
        registry(CLASS_PATH, "compareAndSwapLong", "(Ljava/lang/Object;JJJ)Z", JUnsafeNativeRegistry::compareAndSwapLong);
        registry(CLASS_PATH, "allocateMemory", "(J)J", JUnsafeNativeRegistry::allocateMemory);
        registry(CLASS_PATH, "putLong", "(JJ)V", JUnsafeNativeRegistry::putLong);
        registry(CLASS_PATH, "getByte", "(J)B", JUnsafeNativeRegistry::getByte);
        registry(CLASS_PATH, "freeMemory", "(J)V", JUnsafeNativeRegistry::freeMemory);

    }

    private static void freeMemory(JThread jThread) {
    }

    private static void getByte(JThread jThread) {
        jThread.getJvmStack().pushOperandStackIntVal(1);
    }

    private static void putLong(JThread jThread) {
    }

    private static void allocateMemory(JThread jThread) {
        long byteSize = jThread.getJvmStack().getLocalVarsLongVal(1);
        jThread.getJvmStack().pushOperandStackLongVal(1L);
    }

    private static void compareAndSwapLong(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        Object o = localVars.getRefVal(1).getData();
        Long offset = localVars.getLongVal(2);
        Long expected = localVars.getLongVal(4);
        Long newVal = localVars.getLongVal(6);
        if (o instanceof Slot[]) {
            Long oldVal = (Long) ((Slot[]) o)[offset.intValue()].getVal();
            if (oldVal.equals(expected)) {
                ((Slot[]) o)[offset.intValue()].setVal(newVal);
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else if (o instanceof long[]) {
            Long oldVal = ((long[]) o)[offset.intValue()];
            if (oldVal.equals(expected)) {
                ((long[]) o)[offset.intValue()] = newVal;
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else {
            throw new RuntimeException();
        }
    }

    private static void getObject(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        Object o = localVars.getRefVal(1).getData();
        Long offset = localVars.getLongVal(2);
        JObject ref = null;
        if (o instanceof Slot[]) {
            ref = (JObject) ((Slot[]) o)[offset.intValue()].getVal();
            jThread.getJvmStack().pushOperandStackRefVal(ref);
        } else if (o instanceof JObject[]) {
            ref = ((JObject[]) o)[offset.intValue()];
        } else {
            throw new RuntimeException();
        }
        jThread.getJvmStack().pushOperandStackRefVal(ref);
    }

    private static void compareAndSwapInt(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        Object o = localVars.getRefVal(1).getData();
        Long offset = localVars.getLongVal(2);
        Integer expected = localVars.getIntVal(4);
        Integer newVal = localVars.getIntVal(5);
        if (o instanceof Slot[]) {
            Integer oldVal = (Integer) ((Slot[]) o)[offset.intValue()].getVal();
            if (oldVal.equals(expected)) {
                ((Slot[]) o)[offset.intValue()].setVal(newVal);
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else if (o instanceof int[]) {
            Integer oldVal = ((int[]) o)[offset.intValue()];
            if (oldVal.equals(expected)) {
                ((int[]) o)[offset.intValue()] = newVal;
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else {
            throw new RuntimeException();
        }
    }

    private static void getIntVolatile(JThread jThread) {
        StackFrame stackFrame = jThread.getJvmStack().getTop();
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        Object o = localVars.getRefVal(1).getData();
        Long offset = localVars.getLongVal(2);
        if (o instanceof Slot[]) {
            jThread.getJvmStack().pushOperandStackIntVal((Integer) ((Slot[]) o)[offset.intValue()].getVal());
        } else if (o instanceof int[]) {
            jThread.getJvmStack().pushOperandStackIntVal(((int[]) o)[offset.intValue()]);
        } else {
            throw new RuntimeException();
        }
    }

    private static void compareAndSwapObject(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        JObject obj = localVars.getRefVal(1);
        Object o = obj.getData();
        Long offset = localVars.getLongVal(2);
        JObject expected = localVars.getRefVal(4);
        JObject newVal = localVars.getRefVal(5);
        if (o instanceof Slot[]) {
            JObject jObject = (JObject) ((Slot[]) o)[offset.intValue()].getVal();
            if ((jObject == expected) || (jObject.equals(expected))) {
                ((Slot[]) o)[offset.intValue()].setVal(newVal);
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else if (o instanceof JObject[]) {
            JObject jObject = ((JObject[]) o)[offset.intValue()];
            if ((jObject == expected) || jObject.equals(expected)) {
                ((JObject[]) o)[offset.intValue()] = newVal;
                jThread.getJvmStack().pushOperandStackIntVal(1);
            } else {
                jThread.getJvmStack().pushOperandStackIntVal(0);
            }
        } else {
            throw new RuntimeException("compareAndSwapObject ERROR");
        }
    }

    private static void addressSize(JThread jThread) {
        jThread.getJvmStack().pushOperandStackIntVal(8);
    }

    private static void arrayIndexScale(JThread jThread) {
        jThread.getJvmStack().pushOperandStackIntVal(1);
    }

    private static void arrayBaseOffset(JThread jThread) {
        jThread.getJvmStack().pushOperandStackIntVal(0);
    }

    private static void objectFieldOffset(JThread jThread) {
        JObject ref = ((RefSlot) jThread.getJvmStack().getTop().getLocalVarsVal(1)).getVal();
        JField[] jFields = ref.getJClass().getJFields();
        JField jField = null;
        for (JField jf : jFields) {
            if (jf.getName().equals("slot") && jf.getDescriptor().equals("I")) {
                jField = jf;
                break;
            }
        }
        if (jField == null) {
            throw new RuntimeException();
        }
        int offset = (int) ref.getFields()[jField.getSlotId()].getVal();
        jThread.getJvmStack().pushOperandStackLongVal(Long.valueOf(offset));
    }

    public static JUnsafeNativeRegistry getInstance() {
        return instance;
    }
}
